#ifndef XG_DBCONNECT_CPP
#define XG_DBCONNECT_CPP
//////////////////////////////////////////////////////////////
#include "../DBConnect.h"

static string GetBindString(const string& sysname)
{
	if (sysname == "Oracle")
	{
		string str;
		thread_local int idx = 0;

		if (++idx > 9999) idx = 1;

		stdx::format(str, ":%d", idx);

		return str;
	}

	return "?";
}

string DBData::toValueString(const string& sysname) const
{
	switch(type())
	{
		case DBData_Blob: return GetBindString(sysname);
		case DBData_String: return GetBindString(sysname);
		case DBData_DateTime:
			if (none) return "NULL";
			if (sysname == "MySQL") return stdx::format("str_to_date('%s','%%Y-%%m-%%d %%H:%%i:%%s')", toString().c_str());
			if (sysname == "Oracle") return stdx::format("to_timestamp('%s','YYYY-MM-DD HH24:MI:SS')", toString().c_str());
			return stdx::format("'%s'", toString().c_str());
		default: return none ? "NULL" : toString();
	}
}

const char* ColumnData::getTypeString() const
{
	switch(type)
	{
		case DBData_Blob: return "DBBlob";
		case DBData_Float: return "DBFloat";
		case DBData_Integer: return "DBInteger";
		case DBData_DateTime: return "DBDateTime";
		default: return "DBString";
	}
}

int RowData::getInt(int index)
{
	return (int)getLong(index);
}
float RowData::getFloat(int index)
{
	return (float)getDouble(index);
}
double RowData::getDouble(int index)
{
	return stdx::atof(getString(index).c_str());
}
long long RowData::getLong(int index)
{
	return stdx::atol(getString(index).c_str());
}
SmartBuffer RowData::getBinary(int index)
{
	SmartBuffer buffer;

	int len = getDataLength(index);

	if (len <= 0 || isNull()) return buffer;

	len = getData(index, (char*)buffer.malloc(len), len);

	if (len <= 0) buffer.free();

	return buffer;
}
DateTime RowData::getDateTime(int index)
{
	DateTime datetime;

	getDateTime(datetime, index);

	return std::move(datetime);
}
bool RowData::getDateTime(DateTime& datetime, int index)
{
	datetime = DateTime::FromString(getString(index));

	return datetime.canUse();
}
char* RowData::getString(int index, char* data, int len)
{
	string str = getString(index);

	strncpy(data, str.c_str(), len);
	data[len - 1] = 0;

	return data;
}

string DBConnect::getBindString()
{
	return GetBindString(getSystemName());
}
int DBConnect::exec(const char* sql)
{
	static vector<DBData*> vec;

	return execute(sql, vec);
}
bool DBConnect::begin(bool commited)
{
	if (commited) CHECK_FALSE_RETURN(commit());

	return execute("BEGIN") >= SQLRtn_Duplicate;
}
int DBConnect::release(sp<QueryResult>& rs)
{
	rs = NULL;

	return 0;
}
bool DBConnect::setCharset(const string& charset)
{
	return true;
}
sp<QueryResult> DBConnect::quickQuery(const string& sql)
{
	return query(sql);
}
int DBConnect::bind(void* stmt, const vector<DBData*>& vec)
{
	return 0;
}
int DBConnect::updateStatus(int rowcnt, const string& sqlcmd, const vector<DBData*>& paramlist)
{
	if (status.update(this, rowcnt, sqlcmd, paramlist) < 0)
	{
		if (loglevel <= eERR) LogTrace(eERR, status.toString());
	}
	else
	{
		if (loglevel <= eTIP) LogTrace(eTIP, status.toString());
	}

	return rowcnt;
}

string DBConnect::Status::toString() const
{
	string res;

	if (rowcnt < 0)
	{
		if (errcode)
		{
			stdx::append(res, "execute sqlcmd[%s] failed[%d][%s]", sqlcmd.c_str(), errcode, errmsg.c_str());
		}
		else
		{
			stdx::append(res, "execute sqlcmd[%s] failed[%d]", sqlcmd.c_str(), rowcnt);
		}
	}
	else
	{
		stdx::append(res, "execute sqlcmd[%s] success[%d]", sqlcmd.c_str(), rowcnt);
	}

	if (paramlist.empty()) return res;

	res += " with param";

	for (const string& item : paramlist)
	{
		stdx::append(res, "[%s]", item.length() > 256 ? item.substr(0, 256).c_str() : item.c_str());
	}

	return res;
}
int DBConnect::Status::update(DBConnect* conn, int rowcnt, const string& sqlcmd, const vector<DBData*>& paramlist)
{
	this->rowcnt = rowcnt;
	this->sqlcmd = sqlcmd;
	this->errcode = conn->getErrorCode();
	this->errmsg = conn->getErrorString();

	this->paramlist.clear();

	for (DBData* item : paramlist) this->paramlist.push_back(item->toString());

	return rowcnt;
}

static sp<DBData> GetData(const ReflectItem& attr, const Object& data)
{
	const char* type = attr.getType();
	const char* dest = (char*)(&data) + attr.getOffset();

	if (strcmp(type, "string") == 0) return newsp<DBString>(*(string*)(dest));

	if (strcmp(type, "int") == 0) return newsp<DBInteger>(*(int*)(dest));

	if (strcmp(type, "long") == 0) return newsp<DBInteger>(*(long*)(dest));

	if (strcmp(type, "double") == 0) return newsp<DBFloat>(*(double*)(dest));

	if (strcmp(type, "float") == 0) return newsp<DBFloat>(*(float*)(dest));

	if (strcmp(type, "bool") == 0) return newsp<DBInteger>(*(bool*)(dest) ? 1 : 0);

	return newsp<DBString>(data.toString());
}
int DBConnect::insert(const Object& dest, const char* tabname, const char* exclude, const char* emptynull)
{
	vector<ReflectItem> attrs = ReflectHelper::GetAttrList(&dest);

	if (attrs.empty()) return XG_DATAERR;

	string cols;
	string cond;
	vector<DBData*> params;
	vector<sp<DBData>> holder;
	const string& extag = exclude ? "," + stdx::str(exclude) + "," : "";
	const string& nulltag = emptynull ? "," + stdx::str(emptynull) + "," : "";

	for (ReflectItem& item : attrs)
	{
		const string& name = item.getName();
		const string& ename = "," + name + ",";

		if (extag.find(ename) != string::npos) continue;

		sp<DBData> data = GetData(item, dest);

		if (nulltag.length() > 0 && data->type() == DBData_String && data->toString().empty())
		{
			if (nulltag.find(ename) != string::npos) data->clear();
		}

		params.push_back(data.get());
		holder.push_back(data);
		cols += "," + name;
		cond += ",?";
	}

	if (cols.empty()) return XG_PARAMERR;

	const string& sql = stdx::format("insert into %s(%s) values(%s)", tabname, cols.c_str() + 1, cond.c_str() + 1);

	return execute(sql, params);
}

int DBConnect::update(const Object& dest, const char* tabname, const char* keylist, const char* exclude, const char* emptynull)
{
	vector<ReflectItem> attrs = ReflectHelper::GetAttrList(&dest);

	if (attrs.empty()) return XG_DATAERR;

	string cols;
	string cond = "1=1";
	vector<DBData*> conds;
	vector<DBData*> params;
	vector<sp<DBData>> holder;
	const string& extag = exclude ? "," + stdx::str(exclude) + "," : "";
	const string& keytag = keylist ? "," + stdx::str(keylist) + "," : "";
	const string& nulltag = emptynull ? "," + stdx::str(emptynull) + "," : "";

	for (ReflectItem& item : attrs)
	{
		const string& name = item.getName();
		const string& ename = "," + name + ",";

		if (extag.find(ename) != string::npos) continue;

		sp<DBData> data = GetData(item, dest);

		if (nulltag.length() > 0 && data->type() == DBData_String && data->toString().empty())
		{
			if (nulltag.find(ename) != string::npos) data->clear();
		}

		if (keytag.find(ename) == string::npos)
		{
			params.push_back(data.get());
			cols += "," + name + "=?";
		}
		else
		{
			conds.push_back(data.get());
			cond += " and " + name + "=?";
		}
		
		holder.push_back(data);
	}

	if (cols.empty()) return XG_PARAMERR;

	for (auto& item : conds) params.push_back(item);

	const string& sql = stdx::format("update %s set %s where %s", tabname, cols.c_str() + 1, cond.c_str());

	return execute(sql, params);
}
int DBConnect::replace(const Object& dest, const char* tabname, const char* keylist, const char* exclude, const char* emptynull)
{
	int res = update(dest, tabname, keylist, exclude, emptynull);

	if (res == 0) res = insert(dest, tabname, exclude);

	return res;
}
//////////////////////////////////////////////////////////////
#endif